package com.mgy.example.utils;


import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import sun.misc.BASE64Encoder;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * 二维码生成工具
 * 依赖jar包
 * <dependency>
 * <groupId>com.google.zxing</groupId>
 * <artifactId>core</artifactId>
 * <version>3.3.1</version>
 * </dependency>
 * <dependency>
 * <groupId>com.google.zxing</groupId>
 * <artifactId>javase</artifactId>
 * <version>3.1.0</version>
 * </dependency>
 */
public class QRCodeUtil {
    /**
     * 二维码前景色，默认黑色
     */
    private static final int FORE_COLOR = 0xFF000000;
    /**
     * 二维码背景色，默认白色
     */
    private static final int BACK_COLOR = 0xFFFFFFFF;
    /**
     * 生成二维码默认宽度
     */
    private static final int WIDTH = 200;
    /**
     * 生成二维码默认高度
     */
    private static final int HEIGHT = 200;

    /**
     * png/jpg
     */
    private static final String DEFAULT_FORMAT_NAME = "png";
    /**
     * 用于设置QR二维码参数
     */
    private static Map<EncodeHintType, Object> hints = new HashMap<EncodeHintType, Object>() {
        private static final long serialVersionUID = 1L;

        {
            // 设置QR二维码的纠错级别（H为最高级别）具体级别信息
            put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
            // 设置编码方式
            put(EncodeHintType.CHARACTER_SET, "utf-8");
            //二维码的padding值，四周留白空间,此值不生效，白边框需要自己处理
            put(EncodeHintType.MARGIN, 0);
        }
    };

    /**
     * 去除二维码的白边
     */
    private static BitMatrix deleteWhiteBorder(BitMatrix matrix) {
        int[] rec = matrix.getEnclosingRectangle();
        int resWidth = rec[2] + 1;
        int resHeight = rec[3] + 1;

        BitMatrix resMatrix = new BitMatrix(resWidth, resHeight);
        resMatrix.clear();
        for (int i = 0; i < resWidth; i++) {
            for (int j = 0; j < resHeight; j++) {
                if (matrix.get(i + rec[0], j + rec[1])) {
                    resMatrix.set(i, j);
                }
            }
        }
        return resMatrix;
    }

    /**
     * 根据margin值生成新的二维码
     */
    private static BitMatrix updateBitMatrixByMargin(BitMatrix matrix, int margin) {
        int tempM = margin * 2;
        //获取二维码图案的属性
        int[] rec = matrix.getEnclosingRectangle();
        int resWidth = rec[2] + tempM;
        int resHeight = rec[3] + tempM;
        // 按照自定义边框生成新的BitMatrix
        BitMatrix resMatrix = new BitMatrix(resWidth, resHeight);
        resMatrix.clear();
        //循环，将二维码图案绘制到新的bitMatrix中
        for (int i = margin; i < resWidth - margin; i++) {
            for (int j = margin; j < resHeight - margin; j++) {
                if (matrix.get(i - margin + rec[0], j - margin + rec[1])) {
                    resMatrix.set(i, j);
                }
            }
        }
        return resMatrix;
    }

    /**
     * 图片放大缩小
     */
    public static BufferedImage zoomInImage(BufferedImage originalImage, int width, int height) {
        if (originalImage.getWidth() == width && originalImage.getHeight() == height) {
            return originalImage;
        }
        BufferedImage newImage = new BufferedImage(width, height, originalImage.getType());
        Graphics g = newImage.getGraphics();
        g.drawImage(originalImage, 0, 0, width, height, null);
        g.dispose();
        return newImage;
    }


    /**
     * 可生成生成带logo和底图的二维码图片
     *
     * @param logoFile      logo图地址
     * @param backImageFile 二维码背景图片
     * @param qrCodeUrl     扫描二维码打开的地址
     * @param width         二维码宽
     * @param height        二维码高
     * @param qrCodeX       二维码在背景图的x坐标
     * @param qrCodeY       二维码在背景图的y坐标
     * @param margin        白边边框的宽度
     * @return 最后生成的带logo和背景图片的二维码
     */
    private static BufferedImage drawQRCode(File logoFile, File backImageFile, String qrCodeUrl, Integer width, Integer height, Integer qrCodeX, Integer qrCodeY, int margin) throws IOException, WriterException {
        width = width == null ? WIDTH : width;
        height = height == null ? HEIGHT : height;
        MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
        // 参数顺序分别为：编码内容，编码类型，生成图片宽度，生成图片高度，设置参数
        BitMatrix bm = multiFormatWriter.encode(qrCodeUrl, BarcodeFormat.QR_CODE, width, height, hints);
        if (margin >= 0) {
            bm = updateBitMatrixByMargin(bm, margin);
        }
        BufferedImage bi = MatrixToImageWriter.toBufferedImage(bm);
        //根据size放大、缩放生成的二维码
        BufferedImage image = zoomInImage(bi, width, height);

//        int imgWidth = bm.getWidth();
//        int imgHeight = bm.getHeight();
//
//        // 开始利用二维码数据创建Bitmap图片，分别设为黑（0xFFFFFFFF）白（0xFF000000）两色
//        for (int x = 0; x < imgWidth; x++) {
//            for (int y = 0; y < imgHeight; y++) {
//                image.setRGB(x, y, bm.get(x, y) ? FORE_COLOR : BACK_COLOR);
//            }
//        }
        if (logoFile != null && logoFile.exists()) {
            drawScaledLogoImage(image, logoFile);
        }
        image.flush();
        //绘制背景图
        if (backImageFile != null && backImageFile.exists()) {
            BufferedImage backImage = ImageIO.read(backImageFile);
            return drawBackImage(image, backImage, qrCodeX, qrCodeY);
        }
        return image;

    }

    /**
     * 绘制二维码的logo
     * 如果logo比较大会按比例缩放
     *
     * @param qrCodeImage 二维码图片
     * @param logoFile    logo图片文件
     */
    private static void drawScaledLogoImage(BufferedImage qrCodeImage, File logoFile) throws IOException {
        int imgWidth = qrCodeImage.getWidth();
        int imgHeight = qrCodeImage.getHeight();
        int logoWidth = imgWidth * 2 / 10;
        int logoHeight = imgHeight * 2 / 10;
        // 读取Logo图片
        BufferedImage sourceLogo = ImageIO.read(logoFile);
        if (sourceLogo.getWidth() > logoWidth || sourceLogo.getHeight() > logoHeight) {
            Image image = sourceLogo.getScaledInstance(logoWidth, logoHeight, Image.SCALE_SMOOTH);
            BufferedImage targetLogo = new BufferedImage(logoWidth, logoHeight, BufferedImage.TYPE_INT_RGB);
            Graphics g = targetLogo.getGraphics();
            g.drawImage(image, 0, 0, null);
            g.dispose();
            sourceLogo = targetLogo;
        }
        // 构建绘图对象
        Graphics2D g = qrCodeImage.createGraphics();
        // 开始绘制logo图片
        g.drawImage(sourceLogo, imgWidth * 2 / 5, imgHeight * 2 / 5, logoWidth, logoHeight, null);
        g.dispose();
        sourceLogo.flush();
    }

    /**
     * 二维码绘制背景图
     *
     * @param qrCodeImage 二维码图片
     * @param backImage   二维码背景图
     * @param x           二维码x坐标
     * @param y           二维码y坐标
     */
    private static BufferedImage drawBackImage(BufferedImage qrCodeImage, BufferedImage backImage, Integer x, Integer y) {
        if (qrCodeImage.getWidth() >= backImage.getWidth() || qrCodeImage.getHeight() > backImage.getHeight()) {
            throw new IllegalArgumentException("二维码的宽高大于或等于底图的宽高，无法绘制底图");
        }
        if (x == null) {
            x = (backImage.getWidth() - qrCodeImage.getWidth()) / 2;
        }
        if (y == null) {
            y = (backImage.getHeight() - qrCodeImage.getHeight()) / 2;
        }
        Graphics2D g = backImage.createGraphics();
        g.drawImage(qrCodeImage, x, y, qrCodeImage.getWidth(), qrCodeImage.getHeight(), null);
        g.dispose();
        backImage.flush();
        return backImage;
    }


    /**
     * 生成带logo和底图的二维码图片
     *
     * @param qrCodeFile    二维码文件，保存路径
     * @param logoFile      logo图地址
     * @param backImageFile 二维码背景图片
     * @param qrCodeUrl     扫描二维码打开的地址
     * @param width         二维码宽
     * @param height        二维码高
     * @param qrCodeX       二维码在背景图的x坐标
     * @param qrCodeY       二维码在背景图的y坐标
     * @param margin        是否删除二维码四周的白边
     */
    public static void createQRCode(File qrCodeFile, File logoFile, File backImageFile, String qrCodeUrl, Integer width, Integer height, Integer qrCodeX, Integer qrCodeY, int margin) throws IOException, WriterException {
        BufferedImage image = drawQRCode(logoFile, backImageFile, qrCodeUrl, width, height, qrCodeX, qrCodeY, margin);
        ImageIO.write(image, DEFAULT_FORMAT_NAME, qrCodeFile);
    }

    /**
     * 生成带logo和底图的二维码图片
     *
     * @param logoFile      logo图地址
     * @param backImageFile 二维码背景图片
     * @param qrCodeUrl     扫描二维码打开的地址
     * @param width         二维码宽
     * @param height        二维码高
     * @param qrCodeX       二维码在背景图的x坐标
     * @param qrCodeY       二维码在背景图的y坐标
     * @param margin        是否删除二维码四周的白边
     * @return ByteArrayOutputStream
     */
    public static ByteArrayOutputStream createQRCode(File logoFile, File backImageFile, String qrCodeUrl, Integer width, Integer height, Integer qrCodeX, Integer qrCodeY, int margin) throws IOException, WriterException {
        BufferedImage image = drawQRCode(logoFile, backImageFile, qrCodeUrl, width, height, qrCodeX, qrCodeY, margin);
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ImageIO.write(image, DEFAULT_FORMAT_NAME, outputStream);
        return outputStream;
    }


    /**
     * 生成带logo和底图的二维码图片
     *
     * @param logoFile      logo图地址
     * @param backImageFile 二维码背景图片
     * @param qrCodeUrl     扫描二维码打开的地址
     * @param width         二维码宽
     * @param height        二维码高
     * @param qrCodeX       二维码在背景图的x坐标
     * @param qrCodeY       二维码在背景图的y坐标
     * @param margin        是否删除二维码四周的白边
     * @return base64string
     */
    public static String createQRCodeToBase64String(File logoFile, File backImageFile, String qrCodeUrl, Integer width, Integer height, Integer qrCodeX, Integer qrCodeY, int margin) throws IOException, WriterException {
        BufferedImage image = drawQRCode(logoFile, backImageFile, qrCodeUrl, width, height, qrCodeX, qrCodeY, margin);
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ImageIO.write(image, DEFAULT_FORMAT_NAME, outputStream);
        BASE64Encoder encoder = new BASE64Encoder();
        return encoder.encode(outputStream.toByteArray());
    }

    /**
     * 生成带底图的二维码图片
     *
     * @param backImageFile 二维码背景图片
     * @param qrCodeUrl     扫描二维码打开的地址
     * @param width         二维码宽
     * @param height        二维码高
     * @param qrCodeX       二维码在背景图的x坐标
     * @param qrCodeY       二维码在背景图的y坐标
     * @param margin        是否删除二维码四周的白边
     * @return base64string
     */
    public static String createQRCodeToBase64String(File backImageFile, String qrCodeUrl, Integer width, Integer height, Integer qrCodeX, Integer qrCodeY, int margin) throws IOException, WriterException {
        return createQRCodeToBase64String(null, backImageFile, qrCodeUrl, width, height, qrCodeX, qrCodeY, margin);
    }


    /**
     * 生成指定宽高为200*200的二维码图片
     *
     * @param qrCodeUrl 扫描二维码打开的地址
     * @return base64string
     */
    public static String createQRCodeToBase64String(String qrCodeUrl) throws IOException, WriterException {
        return createQRCodeToBase64String(null, null, qrCodeUrl, null, null, null, null, -1);
    }

    /**
     * 生成固定定宽高的二维码图片
     *
     * @param qrCodeUrl 扫描二维码打开的地址
     * @param width     二维码宽
     * @param height    二维码高
     * @return base64string
     */
    public static String createQRCodeToBase64String(String qrCodeUrl, Integer width, Integer height) throws IOException, WriterException {
        return createQRCodeToBase64String(null, null, qrCodeUrl, width, height, null, null, -1);
    }

    /**
     * 测试
     */
    public static void main(String[] args) throws Exception {
        //头像logo
        // File logoFile = new File("/Users/gaoyuan/Downloads/logo.png");
        //生成二维码地址
        File qrCodeFile = new File("/Users/gaoyuan/Downloads/test.png");

        File backImageFile = new File("/Users/gaoyuan/Downloads/qrCodeBackImage.png");
        String url = "https://www.baidu.com/";
        createQRCode(qrCodeFile, null, backImageFile, url, 128, 128, 35, 51, 0);
    }


}
